#include "lib.h"
#include "serv.h"
#include "GameConnection.h"
#include "BonusBingoConstants.h"
#include "BonusBingoGame.h"
#include "BonusBingoCard.h"
#include "BonusBingoGameResult.h"

#ifdef DEBUG_KNOWNSPINS
#include "BonusBingoGuts.h"
#endif

#include <algorithm>
#include <functional>

#include "getrand.h"

const int32 BonusBingoGame::g_SpecialBallInverseProb = 2200;

WeightedOutcomes BonusBingoGame::MultiplierOutcomes;
WeightedOutcomes BonusBingoGame::JokerOutcomes;

BonusBingoGame::BonusBingoGame(int32 bet)
{
    if ( MultiplierOutcomes.empty() )
    {
        LoadMultiplierOutcomes();
    }

    if ( JokerOutcomes.empty() )
    {
        LoadJokerOutcomes();
    }

    m_betAmount = bet;

	m_jokerWinValue = 0;

    setMultiplierValue();

    memset( &m_cards, 0, sizeof(m_cards) );	

    m_multipliedCardIndex = -1;    
}

BonusBingoGame::BonusBingoGame(int32 bet, uint8 restoredMultiplierValue)
{
    if ( MultiplierOutcomes.empty() )
    {
        LoadMultiplierOutcomes();
    }

    if ( JokerOutcomes.empty() )
    {
        LoadJokerOutcomes();
    }

    m_betAmount = bet;
	
    m_jokerWinValue = 0;

    memset( &m_cards, 0, sizeof(m_cards) );

    //Don't rand this value, we're restoring from saved state here.
    m_multiplierValue = restoredMultiplierValue;

    m_multipliedCardIndex = -1;    
}

void BonusBingoGame::LoadMultiplierOutcomes()
{
    using namespace std;
    MultiplierOutcomes.insert( make_pair( WIN_ONE, 1 ) );
    MultiplierOutcomes.insert( make_pair( WIN_TWO, 2 ) );
    MultiplierOutcomes.insert( make_pair( WIN_THREE, 3 ) );
}

void BonusBingoGame::LoadJokerOutcomes()
{
    using namespace std;
    JokerOutcomes.insert( make_pair( WIN_FIFTY      , 50) );
    JokerOutcomes.insert( make_pair( WIN_TWENTY_FIVE, 25) );
    JokerOutcomes.insert( make_pair( WIN_TEN        , 10) );
    JokerOutcomes.insert( make_pair( WIN_SEVEN      , 7) );
    JokerOutcomes.insert( make_pair( WIN_FIVE       , 5) );
    JokerOutcomes.insert( make_pair( WIN_FOUR       , 4) );
}

BonusBingoGame::~BonusBingoGame()
{
    clearCards();
}

void BonusBingoGame::setMultiplierValue()
{
    uint32 multiplierWon = getrand() % MULTIPLIER_BASE;

    m_multiplierValue = getOutcome( MultiplierOutcomes, multiplierWon, 1 );

#ifdef DEBUG_KNOWNSPINS
    if ( BonusBingoGuts::GetOutcomeValue(BonusBingoGuts::MULTIPLIER) != BonusBingoGuts::Random )
    {
        m_multiplierValue = BonusBingoGuts::GetOutcomeValue(BonusBingoGuts::MULTIPLIER);
    }
#endif
}

BonusBingoCard*  BonusBingoGame::getCard(int8 index) const
{
    if ( index < BonusBingo::NUM_MAX_CARDS )
    {
        return m_cards[index];
    }

    return NULL;
}

int32 BonusBingoGame::addCard(int8 index, BonusBingoCard* const card)
{
    if ( index < BonusBingo::NUM_MAX_CARDS )
    {
        if ( m_cards[index] != NULL )
        {
            delete m_cards[index];
        }

        m_cards[index] = card;
    }
    return -1;
}


void BonusBingoGame::clearCards()
{
    for( int index (0) ; index < BonusBingo::NUM_MAX_CARDS ; ++index )
    {
        delete m_cards[index];
    }

    memset(&m_cards, 0, sizeof( m_cards ));
}


bool8 BonusBingoGame::winsMultiplier()
{
    return m_multiplierValue > 1;
}

void BonusBingoGame::clearMultiplier()
{
    m_multipliedCardIndex = -1;
    m_multiplierValue = 1;
}

void BonusBingoGame::setMultipliedCardIndex(int8 index)
{
    if ( index >= 0 && index < 4)
        m_multipliedCardIndex = index;
}

int8 BonusBingoGame::getMultipliedCardIndex() const
{
    return m_multipliedCardIndex;
}

void BonusBingoGame::setMultipliedCardID(int32 id)
{
    for( int32 index = 0 ; index < 4 ; ++index )
    {
        if ( m_cards[index]->getID() == id )
        {
            m_multipliedCardIndex = index;
            return;
        }
    }

    ASSERT( !"Couldn't set by ID!" );
}

int32 BonusBingoGame::getMultipliedCardID() const
{
    if ( m_multipliedCardIndex >= 0 && m_multipliedCardIndex < 4)
        return m_cards[m_multipliedCardIndex]->getID();
    else
        return 0;
}

uint8 BonusBingoGame::getMultiplierValue() const
{
    return m_multiplierValue;
}

BonusBingoGameResult* BonusBingoGame::play()
{
    BonusBingoGameResult* result = new BonusBingoGameResult(this);

    int32 ballCount (0);

#ifdef DEBUG_KNOWNSPINS
        //Put in ONLY the balls we need to acheive the desired outcome
        for( int32 index = 0 ; index < NUM_MAX_CARDS && ballCount < NUM_BALLS_CHOSEN; ++index )
        {
            int32 outcomeMask = BonusBingoGuts::GetOutcomeValue(index);
            if ( outcomeMask != -1 )
            {
                const BonusBingoCard* card = getCard(index);
                for(int32 col = 0; col < NUM_CARD_COLS ; ++col )
                {
                    for(int32 row = 0; row < NUM_CARD_ROWS; ++row )
                    {
                        if ( row == 2 && col == 2 ) continue;

                        if ( (outcomeMask & HIT_AT(col, row)) == HIT_AT(col, row) )
                        {
                            //Have to add this number to the set
                            int32 winnerBall = card->getValue( col, row );
                            if ( result->addSelectedBall( winnerBall ) )
                            {
                                ++ballCount;
                            }
                        }
                    }
                }
            }
        }

#endif

	uint8 nextBall (0);
    
	do
	{
		nextBall = getrand() % BonusBingo::NUM_BALLS_TOTAL + 1;
#ifndef DEBUG_KNOWNSPINS
		if ( result->addSelectedBall( nextBall ) )
#else
        if ( !result->needsLastBall( nextBall ) 
            && result->addSelectedBall( nextBall ) )
#endif
        {
            ++ballCount;
        }
	}
	while( ballCount < BonusBingo::NUM_BALLS_CHOSEN );

    //We've chosen the balls, now see if there's a joker ball    
    setJokerWinValue();

    result->setJokerValue( getJokerWinValue() );

    result->evaluateGame();

    result->finalizeResults();

    return result;
}

bool8 BonusBingoGame::isValid() const
{
    for( int index (0) ; index < BonusBingo::NUM_MAX_CARDS ; ++index )
    {
        if ( !m_cards[index]->isValid() )
        {
           return false;
        }
    }

    if ( m_multiplierValue > 1 )
    {
        return m_multipliedCardIndex > -1 && m_multipliedCardIndex < 4;
    }

    return true;
}

int32 BonusBingoGame::getJokerWinValue() const
{
	return m_jokerWinValue;
}

void BonusBingoGame::setJokerWinValue()
{
    static uint32 jokerTarget = JOKER_BASE *
        ( 1.0 / g_SpecialBallInverseProb * NUM_BALLS_CHOSEN);

	//ALWAYS start at zero.
	m_jokerWinValue = 0;

    if ( getCardsPlayed() == NUM_MAX_CARDS )
    {
        uint32 jokerDraw = getrand() % JOKER_BASE;

#ifdef DEBUG_KNOWNSPINS
        if ( BonusBingoGuts::GetOutcomeValue(BonusBingoGuts::JOKER_VALUE) != BonusBingoGuts::Loser )
#else
        if ( jokerDraw <= jokerTarget )
#endif
        {
            m_jokerWinValue = getOutcome( JokerOutcomes, getrand() % JOKER_BASE );
#ifdef DEBUG_KNOWNSPINS
            if ( BonusBingoGuts::GetOutcomeValue(BonusBingoGuts::JOKER_VALUE) != BonusBingoGuts::Random )
            {
                m_jokerWinValue = BonusBingoGuts::GetOutcomeValue(BonusBingoGuts::JOKER_VALUE);
            }
#endif
        }
    }
}

bool8 isCardActive(BonusBingoCard* pCard)
{
    return pCard->isActive();
}

uint8 BonusBingoGame::getCardsPlayed() const
{
    return std::count_if( m_cards, m_cards + BonusBingo::NUM_MAX_CARDS, &isCardActive );
}

int32 BonusBingoGame::getBetForCard( int32 cardIndex ) const
{
    if ( getCard( cardIndex )->isActive() )
        return getPerCardBet();
    else
        return 0;
}

int32 BonusBingoGame::getPerCardBet() const
{
	return m_betAmount;
}

int32 BonusBingoGame::getTotalBet() const
{
	return m_betAmount * getCardsPlayed();
}
